﻿using System;
using System.CodeDom;
using System.Linq;

namespace InterfaceWeaver.CustomTool
{
    public static class ICloneableHelper
    {
        /// <summary>
        /// Implements the ICloneable interface.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static CodeTypeDeclaration ImplementICloneable(this CodeTypeDeclaration type)
        {
            if (!type.IsInheriting<ICloneable>())
            {
                type.BaseTypes.Add(new CodeTypeReference(typeof(ICloneable)));
                var cloneMethod = new CodeMemberMethod
                {
                    Name = "Clone",
                    Attributes = MemberAttributes.Public,
                    ReturnType = new CodeTypeReference(typeof(object))
                };

                // Clone instanciation
                var cloneInstanciation = new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(
                    new CodeTypeReferenceExpression(
                           typeof(Activator)),
                           "CreateInstance",
                           new CodeTypeReference(type.Name)));

                // Instance stoarage
                var cloneVariableStorage = new CodeVariableDeclarationStatement(new CodeTypeReference(type.Name), "clone", cloneInstanciation);
                cloneMethod.Statements.Add(cloneVariableStorage);

                cloneMethod.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression(cloneVariableStorage.Name)));
                type.Members.Add(cloneMethod);
            }

            return type;
        }

        /// <summary>
        /// Codes the safe clone assignement sequence.
        /// </summary>
        /// <param name="sourceToClone">The source to clone.</param>
        /// <param name="cloneAssignementTarget">The clone assignement target.</param>
        /// <param name="typeReference">The type reference.</param>
        /// <param name="boxedStorageName">Name of the boxed storage.</param>
        /// <returns></returns>
        public static CodeStatement[] CodeSafeCloneAssignementSequence(
            this CodeExpression sourceToClone, 
            CodeExpression cloneAssignementTarget, 
            CodeTypeReference typeReference,
            string boxedStorageName)
        {
            // Temporary boxed value storage variable declaration
            var boxingStorage = new CodeVariableDeclarationStatement(typeof(object), boxedStorageName)
            {
                InitExpression = sourceToClone
            };

            var safeDeepCloneAssignement = new CodeVariableReferenceExpression(boxingStorage.Name)
                .CodeForcedCloneAssignementSequence(cloneAssignementTarget, typeReference);

            var testICloneable = new CodeConditionStatement(
                new CodeSnippetExpression(string.Format("{0} is {1}", boxingStorage.Name, typeof(ICloneable).FullName)),
                safeDeepCloneAssignement);

            return new CodeStatement[]
                {
                    boxingStorage,
                    testICloneable
                };
        }

        /// <summary>
        /// Codes the forced clone assignement sequence.
        /// </summary>
        /// <param name="sourceToClone">The source to clone.</param>
        /// <param name="cloneAssignementTarget">The clone assignement target.</param>
        /// <param name="typeReference">The type reference.</param>
        /// <returns></returns>
        public static CodeStatement[] CodeForcedCloneAssignementSequence(
            this CodeExpression sourceToClone,
            CodeExpression cloneAssignementTarget,
            CodeTypeReference typeReference)
        {
            var forcedDeepCloneAssignement = new CodeAssignStatement(
                cloneAssignementTarget,
                new CodeCastExpression(typeReference,
                    new CodeMethodInvokeExpression(
                        new CodeCastExpression(typeof(ICloneable), sourceToClone), 
                        "Clone")));

            return new CodeStatement[] {forcedDeepCloneAssignement};
        }

        /// <summary>
        /// Gets the I cloneable clone method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The method declaration or null if the method has not been found</returns>
        public static CodeMemberMethod FindICloneableCloneMethod(this CodeTypeDeclaration type)
        {
            return type.Members
                .OfType<CodeMemberMethod>()
                .FirstOrDefault(method => method.Name == "Clone" && method.Parameters.Count == 0);
        }
    }
}
